from typing import Optional

from chernc.treediff.gumtree.actions.edit_script import EditScript
from chernc.treediff.gumtree.actions.simplified_chawathe_script_generator import SimplifiedChawatheScriptGenerator
from chernc.treediff.gumtree.actions.tree_classifier import TreeClassifier
from chernc.treediff.gumtree.matchers.gumtree_properties import GumtreeProperties
from chernc.treediff.gumtree.matchers.mapping_store import MappingStore
from chernc.treediff.gumtree.matchers.matchers import Matchers
from chernc.treediff.gumtree.tree.tree_context import TreeContext

class Diff:
    """
    Class to facilitate the computation of diffs between ASTs.
    """

    def __init__(self, src: TreeContext, dst: TreeContext,
                 mappings: MappingStore, edit_script: EditScript):
        """
        Instantiate a diff object with the provided source and destination ASTs,
        the provided mappings, and the provided editScript.
        """
        self.src = src
        self.dst = dst
        self.mappings = mappings
        self.edit_script = edit_script

    @staticmethod
    def compute(src_file: str, dst_file: str, tree_generator: str,
                matcher: str, properties: GumtreeProperties) -> 'Diff':
        """
        Compute and return a diff.
        """
        src = TreeGenerators.getInstance().getTree(src_file, tree_generator)
        dst = TreeGenerators.getInstance().getTree(dst_file, tree_generator)
        return Diff._compute(src, dst, tree_generator, matcher, properties)

    @staticmethod
    def _compute(src: TreeContext, dst: TreeContext, tree_generator: str,
                 matcher: str, properties: GumtreeProperties) -> 'Diff':
        """
        Helper method to compute and return a diff between two tree contexts.
        """
        m = Matchers.get_instance().get_matcher_with_fallback(matcher)
        if m is None:
            raise Exception("The matcher is None.")
        m.configure(properties)
        mappings = m.match_with_new_store(src.get_root(), dst.get_root())
        edit_script = SimplifiedChawatheScriptGenerator().compute_actions(mappings)
        return Diff(src, dst, mappings, edit_script)

    @staticmethod
    def compute_with_default(src_file: str, dst_file: str,
                             tree_generator: Optional[str] = None,
                             matcher: Optional[str] = None) -> 'Diff':
        """
        Compute and return a diff using default tree generator and matcher.
        """
        return Diff.compute(src_file, dst_file, tree_generator, matcher, GumtreeProperties())

    @staticmethod
    def compute_default(src_file: str, dst_file: str) -> 'Diff':
        """
        Compute and return a diff using the default matcher and tree generators
        automatically retrieved according to the file extensions.
        """
        return Diff.compute_with_default(src_file, dst_file)

    def create_all_node_classifier(self) -> 'TreeClassifier':
        """
        Compute and return a classifier that indicates which nodes have been added,
        deleted, updated, or moved.
        """
        return AllNodesClassifier(self)

    def create_root_nodes_classifier(self) -> 'TreeClassifier':
        """
        Compute and return a classifier that indicates which nodes have been added,
        deleted, updated, or moved, but only marks the root nodes for whole subtrees.
        """
        return OnlyRootsClassifier(self)
